/* Copyright Joyent, Inc. and other Node contributors. All rights reserved.
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to
 * deal in the Software without restriction, including without limitation the
 * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
 * sell copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
 * IN THE SOFTWARE.
 */

#include <assert.h>
#include <stdlib.h>
#include <direct.h>
#include <errno.h>
#include <fcntl.h>
#include <io.h>
#include <limits.h>
#include <sys/stat.h>
#include <sys/utime.h>
#include <stdio.h>

#include "uv.h"
#include "internal.h"
#include "req-inl.h"
#include "handle-inl.h"

#include <wincrypt.h>

#if USING_VC6RT == 1
#define VOLUME_NAME_DOS 0x0 //default
#endif

#define UV_FS_FREE_PATHS 0x0002
#define UV_FS_FREE_PTR 0x0008
#define UV_FS_CLEANEDUP 0x0010

#define QUEUE_FS_TP_JOB(loop, req)                                           \
    do {                                                                     \
        uv__req_register(loop, req);                                         \
        uv__work_submit((loop), &(req)->work_req, uv__fs_work, uv__fs_done); \
    } while (0)

#define SET_REQ_RESULT(req, result_value)                          \
    do {                                                           \
        req->result = (result_value);                              \
        if (req->result == -1) {                                   \
            req->sys_errno_ = _doserrno;                           \
            req->result = uv_translate_sys_error(req->sys_errno_); \
        }                                                          \
    } while (0)

#define SET_REQ_WIN32_ERROR(req, sys_errno)                    \
    do {                                                       \
        req->sys_errno_ = (sys_errno);                         \
        req->result = uv_translate_sys_error(req->sys_errno_); \
    } while (0)

#define SET_REQ_UV_ERROR(req, uv_errno, sys_errno) \
    do {                                           \
        req->result = (uv_errno);                  \
        req->sys_errno_ = (sys_errno);             \
    } while (0)

#define VERIFY_FD(fd, req)                      \
    if (fd == -1) {                             \
        req->result = UV_EBADF;                 \
        req->sys_errno_ = ERROR_INVALID_HANDLE; \
        return;                                 \
    }

#define FILETIME_TO_UINT(filetime) \
    (*((uint64_t*)&(filetime)) - 116444736000000000ULL)

#define FILETIME_TO_TIME_T(filetime) \
    (FILETIME_TO_UINT(filetime) / 10000000ULL)

#define FILETIME_TO_TIME_NS(filetime, secs) \
    ((FILETIME_TO_UINT(filetime) - (secs * 10000000ULL)) * 100)

#define FILETIME_TO_TIMESPEC(ts, filetime)                               \
    do {                                                                 \
        (ts).tv_sec = (long)FILETIME_TO_TIME_T(filetime);                \
        (ts).tv_nsec = (long)FILETIME_TO_TIME_NS(filetime, (ts).tv_sec); \
    } while (0)

#define TIME_T_TO_FILETIME(time, filetime_ptr)                                   \
    do {                                                                         \
        uint64_t bigtime = ((int64_t)(time)*10000000LL) + 116444736000000000ULL; \
        (filetime_ptr)->dwLowDateTime = bigtime & 0xFFFFFFFF;                    \
        (filetime_ptr)->dwHighDateTime = bigtime >> 32;                          \
    } while (0)

#define IS_SLASH(c) ((c) == L'\\' || (c) == L'/')
#define IS_LETTER(c) (((c) >= L'a' && (c) <= L'z') || ((c) >= L'A' && (c) <= L'Z'))

const WCHAR JUNCTION_PREFIX[] = L"\\??\\";
const WCHAR JUNCTION_PREFIX_LEN = 4;

const WCHAR LONG_PATH_PREFIX[] = L"\\\\?\\";
const WCHAR LONG_PATH_PREFIX_LEN = 4;

const WCHAR UNC_PATH_PREFIX[] = L"\\\\?\\UNC\\";
const WCHAR UNC_PATH_PREFIX_LEN = 8;

void uv_fs_init()
{
    _fmode = _O_BINARY;
}

INLINE static int fs__capture_path(uv_fs_t* req, const char* path,
    const char* new_path, const int copy_path)
{
    char* buf;
    char* pos;
    ssize_t buf_sz = 0, path_len, pathw_len = 0, new_pathw_len = 0;

    /* new_path can only be set if path is also set. */
    assert(new_path == NULL || path != NULL);

    if (path != NULL) {
        pathw_len = MultiByteToWideChar(CP_UTF8,
            0,
            path,
            -1,
            NULL,
            0);
        if (pathw_len == 0) {
            return GetLastError();
        }

        buf_sz += pathw_len * sizeof(WCHAR);
    }

    if (path != NULL && copy_path) {
        path_len = 1 + strlen(path);
        buf_sz += path_len;
    }

    if (new_path != NULL) {
        new_pathw_len = MultiByteToWideChar(CP_UTF8,
            0,
            new_path,
            -1,
            NULL,
            0);
        if (new_pathw_len == 0) {
            return GetLastError();
        }

        buf_sz += new_pathw_len * sizeof(WCHAR);
    }

    if (buf_sz == 0) {
        req->file.pathw = NULL;
        req->fs.info.new_pathw = NULL;
        req->path = NULL;
        return 0;
    }

    buf = (char*)uv__malloc(buf_sz);
    if (buf == NULL) {
        return ERROR_OUTOFMEMORY;
    }

    pos = buf;

    if (path != NULL) {
        DWORD r = MultiByteToWideChar(CP_UTF8,
            0,
            path,
            -1,
            (WCHAR*)pos,
            pathw_len);
        assert(r == (DWORD)pathw_len);
        req->file.pathw = (WCHAR*)pos;
        pos += r * sizeof(WCHAR);
    } else {
        req->file.pathw = NULL;
    }

    if (new_path != NULL) {
        DWORD r = MultiByteToWideChar(CP_UTF8,
            0,
            new_path,
            -1,
            (WCHAR*)pos,
            new_pathw_len);
        assert(r == (DWORD)new_pathw_len);
        req->fs.info.new_pathw = (WCHAR*)pos;
        pos += r * sizeof(WCHAR);
    } else {
        req->fs.info.new_pathw = NULL;
    }

    if (!copy_path) {
        req->path = path;
    } else if (path) {
        memcpy(pos, path, path_len);
        assert(path_len == buf_sz - (pos - buf));
        req->path = pos;
    } else {
        req->path = NULL;
    }

    req->flags |= UV_FS_FREE_PATHS;

    return 0;
}

INLINE static void uv_fs_req_init(uv_loop_t* loop, uv_fs_t* req,
    uv_fs_type fs_type, const uv_fs_cb cb)
{
    uv_req_init(loop, (uv_req_t*)req);

    req->type = UV_FS;
    req->loop = loop;
    req->flags = 0;
    req->fs_type = fs_type;
    req->result = 0;
    req->ptr = NULL;
    req->path = NULL;
    req->cb = cb;
}

static int fs__wide_to_utf8(WCHAR* w_source_ptr,
    DWORD w_source_len,
    char** target_ptr,
    uint64_t* target_len_ptr)
{
    int r;
    int target_len;
    char* target;
    target_len = WideCharToMultiByte(CP_UTF8,
        0,
        w_source_ptr,
        w_source_len,
        NULL,
        0,
        NULL,
        NULL);

    if (target_len == 0) {
        return -1;
    }

    if (target_len_ptr != NULL) {
        *target_len_ptr = target_len;
    }

    if (target_ptr == NULL) {
        return 0;
    }

    target = uv__malloc(target_len + 1);
    if (target == NULL) {
        SetLastError(ERROR_OUTOFMEMORY);
        return -1;
    }

    r = WideCharToMultiByte(CP_UTF8,
        0,
        w_source_ptr,
        w_source_len,
        target,
        target_len,
        NULL,
        NULL);
    assert(r == target_len);
    target[target_len] = '\0';
    *target_ptr = target;
    return 0;
}

INLINE static int fs__readlink_handle(HANDLE handle, char** target_ptr,
    uint64_t* target_len_ptr)
{
    char buffer[MAXIMUM_REPARSE_DATA_BUFFER_SIZE];
    REPARSE_DATA_BUFFER* reparse_data = (REPARSE_DATA_BUFFER*)buffer;
    WCHAR* w_target;
    DWORD w_target_len;
    DWORD bytes;

    if (!DeviceIoControl(handle,
            FSCTL_GET_REPARSE_POINT,
            NULL,
            0,
            buffer,
            sizeof buffer,
            &bytes,
            NULL)) {
        return -1;
    }

    if (reparse_data->ReparseTag == IO_REPARSE_TAG_SYMLINK) {
        /* Real symlink */
        w_target = reparse_data->SymbolicLinkReparseBuffer.PathBuffer + (reparse_data->SymbolicLinkReparseBuffer.SubstituteNameOffset / sizeof(WCHAR));
        w_target_len = reparse_data->SymbolicLinkReparseBuffer.SubstituteNameLength / sizeof(WCHAR);

        /* Real symlinks can contain pretty much everything, but the only thing */
        /* we really care about is undoing the implicit conversion to an NT */
        /* namespaced path that CreateSymbolicLink will perform on absolute */
        /* paths. If the path is win32-namespaced then the user must have */
        /* explicitly made it so, and we better just return the unmodified */
        /* reparse data. */
        if (w_target_len >= 4 && w_target[0] == L'\\' && w_target[1] == L'?' && w_target[2] == L'?' && w_target[3] == L'\\') {
            /* Starts with \??\ */
            if (w_target_len >= 6 && ((w_target[4] >= L'A' && w_target[4] <= L'Z') || (w_target[4] >= L'a' && w_target[4] <= L'z')) && w_target[5] == L':' && (w_target_len == 6 || w_target[6] == L'\\')) {
                /* \??\<drive>:\ */
                w_target += 4;
                w_target_len -= 4;

            } else if (w_target_len >= 8 && (w_target[4] == L'U' || w_target[4] == L'u') && (w_target[5] == L'N' || w_target[5] == L'n') && (w_target[6] == L'C' || w_target[6] == L'c') && w_target[7] == L'\\') {
                /* \??\UNC\<server>\<share>\ - make sure the final path looks like */
                /* \\<server>\<share>\ */
                w_target += 6;
                w_target[0] = L'\\';
                w_target_len -= 6;
            }
        }

    } else if (reparse_data->ReparseTag == IO_REPARSE_TAG_MOUNT_POINT) {
        /* Junction. */
        w_target = reparse_data->MountPointReparseBuffer.PathBuffer + (reparse_data->MountPointReparseBuffer.SubstituteNameOffset / sizeof(WCHAR));
        w_target_len = reparse_data->MountPointReparseBuffer.SubstituteNameLength / sizeof(WCHAR);

        /* Only treat junctions that look like \??\<drive>:\ as symlink. */
        /* Junctions can also be used as mount points, like \??\Volume{<guid>}, */
        /* but that's confusing for programs since they wouldn't be able to */
        /* actually understand such a path when returned by uv_readlink(). */
        /* UNC paths are never valid for junctions so we don't care about them. */
        if (!(w_target_len >= 6 && w_target[0] == L'\\' && w_target[1] == L'?' && w_target[2] == L'?' && w_target[3] == L'\\' && ((w_target[4] >= L'A' && w_target[4] <= L'Z') || (w_target[4] >= L'a' && w_target[4] <= L'z')) && w_target[5] == L':' && (w_target_len == 6 || w_target[6] == L'\\'))) {
            SetLastError(ERROR_SYMLINK_NOT_SUPPORTED);
            return -1;
        }

        /* Remove leading \??\ */
        w_target += 4;
        w_target_len -= 4;

    } else {
        /* Reparse tag does not indicate a symlink. */
        SetLastError(ERROR_SYMLINK_NOT_SUPPORTED);
        return -1;
    }

    return fs__wide_to_utf8(w_target, w_target_len, target_ptr, target_len_ptr);
}

void fs__open(uv_fs_t* req)
{
    DWORD access;
    DWORD share;
    DWORD disposition;
    DWORD attributes = 0;
    HANDLE file;
    int fd, current_umask;
    int flags = req->fs.info.file_flags;

    /* Obtain the active umask. umask() never fails and returns the previous */
    /* umask. */
    current_umask = umask(0);
    umask(current_umask);

    /* convert flags and mode to CreateFile parameters */
    switch (flags & (_O_RDONLY | _O_WRONLY | _O_RDWR)) {
    case _O_RDONLY:
        access = FILE_GENERIC_READ;
        attributes |= FILE_FLAG_BACKUP_SEMANTICS;
        break;
    case _O_WRONLY:
        access = FILE_GENERIC_WRITE;
        break;
    case _O_RDWR:
        access = FILE_GENERIC_READ | FILE_GENERIC_WRITE;
        break;
    default:
        goto einval;
    }

    if (flags & _O_APPEND) {
        access &= ~FILE_WRITE_DATA;
        access |= FILE_APPEND_DATA;
        attributes &= ~FILE_FLAG_BACKUP_SEMANTICS;
    }

    /*
   * Here is where we deviate significantly from what CRT's _open()
   * does. We indiscriminately use all the sharing modes, to match
   * UNIX semantics. In particular, this ensures that the file can
   * be deleted even whilst it's open, fixing issue #1449.
   */
    share = FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE;

    switch (flags & (_O_CREAT | _O_EXCL | _O_TRUNC)) {
    case 0:
    case _O_EXCL:
        disposition = OPEN_EXISTING;
        break;
    case _O_CREAT:
        disposition = OPEN_ALWAYS;
        break;
    case _O_CREAT | _O_EXCL:
    case _O_CREAT | _O_TRUNC | _O_EXCL:
        disposition = CREATE_NEW;
        break;
    case _O_TRUNC:
    case _O_TRUNC | _O_EXCL:
        disposition = TRUNCATE_EXISTING;
        break;
    case _O_CREAT | _O_TRUNC:
        disposition = CREATE_ALWAYS;
        break;
    default:
        goto einval;
    }

    attributes |= FILE_ATTRIBUTE_NORMAL;
    if (flags & _O_CREAT) {
        if (!((req->fs.info.mode & ~current_umask) & _S_IWRITE)) {
            attributes |= FILE_ATTRIBUTE_READONLY;
        }
    }

    if (flags & _O_TEMPORARY) {
        attributes |= FILE_FLAG_DELETE_ON_CLOSE | FILE_ATTRIBUTE_TEMPORARY;
        access |= DELETE;
    }

    if (flags & _O_SHORT_LIVED) {
        attributes |= FILE_ATTRIBUTE_TEMPORARY;
    }

    switch (flags & (_O_SEQUENTIAL | _O_RANDOM)) {
    case 0:
        break;
    case _O_SEQUENTIAL:
        attributes |= FILE_FLAG_SEQUENTIAL_SCAN;
        break;
    case _O_RANDOM:
        attributes |= FILE_FLAG_RANDOM_ACCESS;
        break;
    default:
        goto einval;
    }

    /* Setting this flag makes it possible to open a directory. */
    attributes |= FILE_FLAG_BACKUP_SEMANTICS;

    file = CreateFileW(req->file.pathw,
        access,
        share,
        NULL,
        disposition,
        attributes,
        NULL);
    if (file == INVALID_HANDLE_VALUE) {
        DWORD error = GetLastError();
        if (error == ERROR_FILE_EXISTS && (flags & _O_CREAT) && !(flags & _O_EXCL)) {
            /* Special case: when ERROR_FILE_EXISTS happens and O_CREAT was */
            /* specified, it means the path referred to a directory. */
            SET_REQ_UV_ERROR(req, UV_EISDIR, error);
        } else {
            SET_REQ_WIN32_ERROR(req, GetLastError());
        }
        return;
    }

    fd = _open_osfhandle((intptr_t)file, flags);
    if (fd < 0) {
        /* The only known failure mode for _open_osfhandle() is EMFILE, in which
     * case GetLastError() will return zero. However we'll try to handle other
     * errors as well, should they ever occur.
     */
        if (errno == EMFILE)
            SET_REQ_UV_ERROR(req, UV_EMFILE, ERROR_TOO_MANY_OPEN_FILES);
        else if (GetLastError() != ERROR_SUCCESS)
            SET_REQ_WIN32_ERROR(req, GetLastError());
        else
            SET_REQ_WIN32_ERROR(req, UV_UNKNOWN);
        CloseHandle(file);
        return;
    }

    SET_REQ_RESULT(req, fd);
    return;

einval:
    SET_REQ_UV_ERROR(req, UV_EINVAL, ERROR_INVALID_PARAMETER);
}

void fs__close(uv_fs_t* req)
{
    int fd = req->file.fd;
    int result;

    VERIFY_FD(fd, req);

    if (fd > 2)
        result = _close(fd);
    else
        result = 0;

    /* _close doesn't set _doserrno on failure, but it does always set errno
   * to EBADF on failure.
   */
    if (result == -1) {
        assert(errno == EBADF);
        SET_REQ_UV_ERROR(req, UV_EBADF, ERROR_INVALID_HANDLE);
    } else {
        req->result = 0;
    }
}

void fs__read(uv_fs_t* req)
{
    int fd = req->file.fd;
    int64_t offset = req->fs.info.offset;
    HANDLE handle;
    OVERLAPPED overlapped, *overlapped_ptr;
    LARGE_INTEGER offset_;
    DWORD bytes;
    DWORD error;
    int result;
    unsigned int index;

    VERIFY_FD(fd, req);

    handle = uv__get_osfhandle(fd);

    if (handle == INVALID_HANDLE_VALUE) {
        SET_REQ_WIN32_ERROR(req, ERROR_INVALID_HANDLE);
        return;
    }

    if (offset != -1) {
        memset(&overlapped, 0, sizeof overlapped);
        overlapped_ptr = &overlapped;
    } else {
        overlapped_ptr = NULL;
    }

    index = 0;
    bytes = 0;
    do {
        DWORD incremental_bytes;

        if (offset != -1) {
            offset_.QuadPart = offset + bytes;
            overlapped.Offset = offset_.LowPart;
            overlapped.OffsetHigh = offset_.HighPart;
        }

        result = ReadFile(handle,
            req->fs.info.bufs[index].base,
            req->fs.info.bufs[index].len,
            &incremental_bytes,
            overlapped_ptr);
        bytes += incremental_bytes;
        ++index;
    } while (result && index < req->fs.info.nbufs);

    if (result || bytes > 0) {
        SET_REQ_RESULT(req, bytes);
    } else {
        error = GetLastError();
        if (error == ERROR_HANDLE_EOF) {
            SET_REQ_RESULT(req, bytes);
        } else {
            SET_REQ_WIN32_ERROR(req, error);
        }
    }
}

static wchar_t* UTF8ToUTF16(const char* utf8)
{
    int size = strlen(utf8);
    size_t n = MultiByteToWideChar(CP_UTF8, 0, utf8, size, 0, 0);

    size_t wbufLen = (n + 1) * sizeof(wchar_t);
    wchar_t* wbuf = (wchar_t*)malloc(wbufLen);
    memset(wbuf, 0, wbufLen);
    MultiByteToWideChar(CP_UTF8, 0, utf8, size, wbuf, n);

    return wbuf;
}

void fs__write(uv_fs_t* req)
{
    int fd = req->file.fd;
    int64_t offset = req->fs.info.offset;
    HANDLE handle;
    OVERLAPPED overlapped, *overlapped_ptr;
    LARGE_INTEGER offset_;
    DWORD bytes;
    int result;
    unsigned int index;

    VERIFY_FD(fd, req);

    handle = uv__get_osfhandle(fd);
    if (handle == INVALID_HANDLE_VALUE && (1 != fd && 2 != fd)) {
        SET_REQ_WIN32_ERROR(req, ERROR_INVALID_HANDLE);
        return;
    }

    if (offset != -1) {
        memset(&overlapped, 0, sizeof overlapped);
        overlapped_ptr = &overlapped;
    } else {
        overlapped_ptr = NULL;
    }

    index = 0;
    bytes = 0;
    do {
        DWORD incremental_bytes;

        if (offset != -1) {
            offset_.QuadPart = offset + bytes;
            overlapped.Offset = offset_.LowPart;
            overlapped.OffsetHigh = offset_.HighPart;
        }

        ULONG len = req->fs.info.bufs[index].len;
        char* base = req->fs.info.bufs[index].base;
        if (/*(HANDLE)0xfffffffe == handle &&*/ (1 == fd || 2 == fd)) {
            char* output = malloc(len + 2);
            output[len] = '\n';
            output[len + 1] = 0;
            strncpy(output, base, len);
            if ('\n' == output[len - 1])
                output[len] = 0;

            wchar_t* tempW = UTF8ToUTF16(output);
            wchar_t* outputW = malloc(sizeof(wchar_t) * (100 + wcslen(tempW)));
            wcscpy(outputW, L"fs__write console.log:");
            wcscat(outputW, tempW);
            OutputDebugStringW(outputW);

            free(output);
            free(tempW);
            free(outputW);

            incremental_bytes = len;
            result = 1;
        } else {
            result = WriteFile(handle, base, len, &incremental_bytes, overlapped_ptr);
        }

        bytes += incremental_bytes;
        ++index;
    } while (result && index < req->fs.info.nbufs);

    if (result || bytes > 0) {
        SET_REQ_RESULT(req, bytes);
    } else {
        SET_REQ_WIN32_ERROR(req, GetLastError());
    }
}

// int uv_fs_copyfile(uv_loop_t* loop,
//     uv_fs_t* req,
//     const char* path,
//     const char* new_path,
//     int flags,
//     uv_fs_cb cb)
// {
//     int err;
//
//     INIT(UV_FS_COPYFILE);
//
//     if (flags & ~(UV_FS_COPYFILE_EXCL |
//         UV_FS_COPYFILE_FICLONE |
//         UV_FS_COPYFILE_FICLONE_FORCE)) {
//         return UV_EINVAL;
//     }
//
//     err = fs__capture_path(req, path, new_path, cb != NULL);
//
//     if (err)
//         return uv_translate_sys_error(err);
//
//     req->fs.info.file_flags = flags;
//     POST;
// }

void fs__rmdir(uv_fs_t* req)
{
    int result = _wrmdir(req->file.pathw);
    SET_REQ_RESULT(req, result);
}

void fs__unlink(uv_fs_t* req)
{
    const WCHAR* pathw = req->file.pathw;
    HANDLE handle;
    BY_HANDLE_FILE_INFORMATION info;
    FILE_DISPOSITION_INFORMATION disposition;
    IO_STATUS_BLOCK iosb;
    NTSTATUS status;

    handle = CreateFileW(pathw,
        FILE_READ_ATTRIBUTES | FILE_WRITE_ATTRIBUTES | DELETE,
        FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
        NULL,
        OPEN_EXISTING,
        FILE_FLAG_OPEN_REPARSE_POINT | FILE_FLAG_BACKUP_SEMANTICS,
        NULL);

    if (handle == INVALID_HANDLE_VALUE) {
        SET_REQ_WIN32_ERROR(req, GetLastError());
        return;
    }

    if (!GetFileInformationByHandle(handle, &info)) {
        SET_REQ_WIN32_ERROR(req, GetLastError());
        CloseHandle(handle);
        return;
    }

    if (info.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
        /* Do not allow deletion of directories, unless it is a symlink. When */
        /* the path refers to a non-symlink directory, report EPERM as mandated */
        /* by POSIX.1. */

        /* Check if it is a reparse point. If it's not, it's a normal directory. */
        if (!(info.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT)) {
            SET_REQ_WIN32_ERROR(req, ERROR_ACCESS_DENIED);
            CloseHandle(handle);
            return;
        }

        /* Read the reparse point and check if it is a valid symlink. */
        /* If not, don't unlink. */
        if (fs__readlink_handle(handle, NULL, NULL) < 0) {
            DWORD error = GetLastError();
            if (error == ERROR_SYMLINK_NOT_SUPPORTED)
                error = ERROR_ACCESS_DENIED;
            SET_REQ_WIN32_ERROR(req, error);
            CloseHandle(handle);
            return;
        }
    }

    if (info.dwFileAttributes & FILE_ATTRIBUTE_READONLY) {
        /* Remove read-only attribute */
        FILE_BASIC_INFORMATION basic = { 0 };

        basic.FileAttributes = info.dwFileAttributes & ~(FILE_ATTRIBUTE_READONLY);

        status = pNtSetInformationFile(handle,
            &iosb,
            &basic,
            sizeof basic,
            FileBasicInformation);
        if (!NT_SUCCESS(status)) {
            SET_REQ_WIN32_ERROR(req, pRtlNtStatusToDosError(status));
            CloseHandle(handle);
            return;
        }
    }

    /* Try to set the delete flag. */
    disposition.DeleteFile = TRUE;
    status = pNtSetInformationFile(handle,
        &iosb,
        &disposition,
        sizeof disposition,
        FileDispositionInformation);
    if (NT_SUCCESS(status)) {
        SET_REQ_SUCCESS(req);
    } else {
        SET_REQ_WIN32_ERROR(req, pRtlNtStatusToDosError(status));
    }

    CloseHandle(handle);
}

void fs__mkdir(uv_fs_t* req)
{
    /* TODO: use req->mode. */
    int result = _wmkdir(req->file.pathw);
    SET_REQ_RESULT(req, result);
}

/* OpenBSD original: lib/libc/stdio/mktemp.c */
void fs__mkdtemp(uv_fs_t* req)
{
    static const WCHAR* tempchars = L"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
    static const size_t num_chars = 62;
    static const size_t num_x = 6;
    WCHAR *cp, *ep;
    unsigned int tries, i;
    size_t len;
    HCRYPTPROV h_crypt_prov;
    uint64_t v;
    BOOL released;

    len = wcslen(req->file.pathw);
    ep = req->file.pathw + len;
    if (len < num_x || wcsncmp(ep - num_x, L"XXXXXX", num_x)) {
        SET_REQ_UV_ERROR(req, UV_EINVAL, ERROR_INVALID_PARAMETER);
        return;
    }

    if (!CryptAcquireContext(&h_crypt_prov, NULL, NULL, PROV_RSA_FULL,
            CRYPT_VERIFYCONTEXT)) {
        SET_REQ_WIN32_ERROR(req, GetLastError());
        return;
    }

    tries = TMP_MAX;
    do {
        if (!CryptGenRandom(h_crypt_prov, sizeof(v), (BYTE*)&v)) {
            SET_REQ_WIN32_ERROR(req, GetLastError());
            break;
        }

        cp = ep - num_x;
        for (i = 0; i < num_x; i++) {
            *cp++ = tempchars[v % num_chars];
            v /= num_chars;
        }

        if (_wmkdir(req->file.pathw) == 0) {
            len = strlen(req->path);
            wcstombs((char*)req->path + len - num_x, ep - num_x, num_x);
            SET_REQ_RESULT(req, 0);
            break;
        } else if (errno != EEXIST) {
            SET_REQ_RESULT(req, -1);
            break;
        }
    } while (--tries);

    released = CryptReleaseContext(h_crypt_prov, 0);
    assert(released);
    if (tries == 0) {
        SET_REQ_RESULT(req, -1);
    }
}

void fs__scandir(uv_fs_t* req)
{
    static const size_t dirents_initial_size = 32;

    HANDLE dir_handle = INVALID_HANDLE_VALUE;

    uv__dirent_t** dirents = NULL;
    size_t dirents_size = 0;
    size_t dirents_used = 0;

    IO_STATUS_BLOCK iosb;
    NTSTATUS status;

    /* Buffer to hold directory entries returned by NtQueryDirectoryFile.
   * It's important that this buffer can hold at least one entry, regardless
   * of the length of the file names present in the enumerated directory.
   * A file name is at most 256 WCHARs long.
   * According to MSDN, the buffer must be aligned at an 8-byte boundary.
   */
#if _MSC_VER
    __declspec(align(8)) char buffer[8192];
#else
    __attribute__((aligned(8))) char buffer[8192];
#endif

    STATIC_ASSERT(sizeof buffer >= sizeof(FILE_DIRECTORY_INFORMATION) + 256 * sizeof(WCHAR));

    /* Open the directory. */
    dir_handle = CreateFileW(req->file.pathw,
        FILE_LIST_DIRECTORY | SYNCHRONIZE,
        FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
        NULL,
        OPEN_EXISTING,
        FILE_FLAG_BACKUP_SEMANTICS,
        NULL);
    if (dir_handle == INVALID_HANDLE_VALUE)
        goto win32_error;

    /* Read the first chunk. */
    status = pNtQueryDirectoryFile(dir_handle,
        NULL,
        NULL,
        NULL,
        &iosb,
        &buffer,
        sizeof buffer,
        FileDirectoryInformation,
        FALSE,
        NULL,
        TRUE);

    /* If the handle is not a directory, we'll get STATUS_INVALID_PARAMETER.
   * This should be reported back as UV_ENOTDIR.
   */
    if (status == STATUS_INVALID_PARAMETER)
        goto not_a_directory_error;

    while (NT_SUCCESS(status)) {
        char* position = buffer;
        size_t next_entry_offset = 0;

        do {
            FILE_DIRECTORY_INFORMATION* info;
            uv__dirent_t* dirent;

            size_t wchar_len;
            size_t utf8_len;

            /* Obtain a pointer to the current directory entry. */
            position += next_entry_offset;
            info = (FILE_DIRECTORY_INFORMATION*)position;

            /* Fetch the offset to the next directory entry. */
            next_entry_offset = info->NextEntryOffset;

            /* Compute the length of the filename in WCHARs. */
            wchar_len = info->FileNameLength / sizeof info->FileName[0];

            /* Skip over '.' and '..' entries.  It has been reported that
       * the SharePoint driver includes the terminating zero byte in
       * the filename length.  Strip those first.
       */
            while (wchar_len > 0 && info->FileName[wchar_len - 1] == L'\0')
                wchar_len -= 1;

            if (wchar_len == 0)
                continue;
            if (wchar_len == 1 && info->FileName[0] == L'.')
                continue;
            if (wchar_len == 2 && info->FileName[0] == L'.' && info->FileName[1] == L'.')
                continue;

            /* Compute the space required to store the filename as UTF-8. */
            utf8_len = WideCharToMultiByte(
                CP_UTF8, 0, &info->FileName[0], wchar_len, NULL, 0, NULL, NULL);
            if (utf8_len == 0)
                goto win32_error;

            /* Resize the dirent array if needed. */
            if (dirents_used >= dirents_size) {
                size_t new_dirents_size = dirents_size == 0 ? dirents_initial_size : dirents_size << 1;
                uv__dirent_t** new_dirents = uv__realloc(dirents, new_dirents_size * sizeof *dirents);

                if (new_dirents == NULL)
                    goto out_of_memory_error;

                dirents_size = new_dirents_size;
                dirents = new_dirents;
            }

            /* Allocate space for the uv dirent structure. The dirent structure
       * includes room for the first character of the filename, but `utf8_len`
       * doesn't count the NULL terminator at this point.
       */
            dirent = uv__malloc(sizeof *dirent + utf8_len);
            if (dirent == NULL)
                goto out_of_memory_error;

            dirents[dirents_used++] = dirent;

            /* Convert file name to UTF-8. */
            if (WideCharToMultiByte(CP_UTF8,
                    0,
                    &info->FileName[0],
                    wchar_len,
                    &dirent->d_name[0],
                    utf8_len,
                    NULL,
                    NULL)
                == 0)
                goto win32_error;

            /* Add a null terminator to the filename. */
            dirent->d_name[utf8_len] = '\0';

            /* Fill out the type field. */
            if (info->FileAttributes & FILE_ATTRIBUTE_DEVICE)
                dirent->d_type = UV__DT_CHAR;
            else if (info->FileAttributes & FILE_ATTRIBUTE_REPARSE_POINT)
                dirent->d_type = UV__DT_LINK;
            else if (info->FileAttributes & FILE_ATTRIBUTE_DIRECTORY)
                dirent->d_type = UV__DT_DIR;
            else
                dirent->d_type = UV__DT_FILE;
        } while (next_entry_offset != 0);

        /* Read the next chunk. */
        status = pNtQueryDirectoryFile(dir_handle,
            NULL,
            NULL,
            NULL,
            &iosb,
            &buffer,
            sizeof buffer,
            FileDirectoryInformation,
            FALSE,
            NULL,
            FALSE);

        /* After the first pNtQueryDirectoryFile call, the function may return
     * STATUS_SUCCESS even if the buffer was too small to hold at least one
     * directory entry.
     */
        if (status == STATUS_SUCCESS && iosb.Information == 0)
            status = STATUS_BUFFER_OVERFLOW;
    }

    if (status != STATUS_NO_MORE_FILES)
        goto nt_error;

    CloseHandle(dir_handle);

    /* Store the result in the request object. */
    req->ptr = dirents;
    if (dirents != NULL)
        req->flags |= UV_FS_FREE_PTR;

    SET_REQ_RESULT(req, dirents_used);

    /* `nbufs` will be used as index by uv_fs_scandir_next. */
    req->fs.info.nbufs = 0;

    return;

nt_error:
    SET_REQ_WIN32_ERROR(req, pRtlNtStatusToDosError(status));
    goto cleanup;

win32_error:
    SET_REQ_WIN32_ERROR(req, GetLastError());
    goto cleanup;

not_a_directory_error:
    SET_REQ_UV_ERROR(req, UV_ENOTDIR, ERROR_DIRECTORY);
    goto cleanup;

out_of_memory_error:
    SET_REQ_UV_ERROR(req, UV_ENOMEM, ERROR_OUTOFMEMORY);
    goto cleanup;

cleanup:
    if (dir_handle != INVALID_HANDLE_VALUE)
        CloseHandle(dir_handle);
    while (dirents_used > 0)
        uv__free(dirents[--dirents_used]);
    if (dirents != NULL)
        uv__free(dirents);
}

INLINE static int fs__stat_handle(HANDLE handle, uv_stat_t* statbuf)
{
    FILE_ALL_INFORMATION file_info;
    FILE_FS_VOLUME_INFORMATION volume_info;
    NTSTATUS nt_status;
    IO_STATUS_BLOCK io_status;

    nt_status = pNtQueryInformationFile(handle,
        &io_status,
        &file_info,
        sizeof file_info,
        FileAllInformation);

    /* Buffer overflow (a warning status code) is expected here. */
    if (NT_ERROR(nt_status)) {
        SetLastError(pRtlNtStatusToDosError(nt_status));
        return -1;
    }

    nt_status = pNtQueryVolumeInformationFile(handle,
        &io_status,
        &volume_info,
        sizeof volume_info,
        FileFsVolumeInformation);

    /* Buffer overflow (a warning status code) is expected here. */
    if (io_status.Status == STATUS_NOT_IMPLEMENTED) {
        statbuf->st_dev = 0;
    } else if (NT_ERROR(nt_status)) {
        SetLastError(pRtlNtStatusToDosError(nt_status));
        return -1;
    } else {
        statbuf->st_dev = volume_info.VolumeSerialNumber;
    }

    /* Todo: st_mode should probably always be 0666 for everyone. We might also
   * want to report 0777 if the file is a .exe or a directory.
   *
   * Currently it's based on whether the 'readonly' attribute is set, which
   * makes little sense because the semantics are so different: the 'read-only'
   * flag is just a way for a user to protect against accidental deletion, and
   * serves no security purpose. Windows uses ACLs for that.
   *
   * Also people now use uv_fs_chmod() to take away the writable bit for good
   * reasons. Windows however just makes the file read-only, which makes it
   * impossible to delete the file afterwards, since read-only files can't be
   * deleted.
   *
   * IOW it's all just a clusterfuck and we should think of something that
   * makes slightly more sense.
   *
   * And uv_fs_chmod should probably just fail on windows or be a total no-op.
   * There's nothing sensible it can do anyway.
   */
    statbuf->st_mode = 0;

    if (file_info.BasicInformation.FileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) {
        statbuf->st_mode |= S_IFLNK;
        if (fs__readlink_handle(handle, NULL, &statbuf->st_size) != 0)
            return -1;

    } else if (file_info.BasicInformation.FileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
        statbuf->st_mode |= _S_IFDIR;
        statbuf->st_size = 0;

    } else {
        statbuf->st_mode |= _S_IFREG;
        statbuf->st_size = file_info.StandardInformation.EndOfFile.QuadPart;
    }

    if (file_info.BasicInformation.FileAttributes & FILE_ATTRIBUTE_READONLY)
        statbuf->st_mode |= _S_IREAD | (_S_IREAD >> 3) | (_S_IREAD >> 6);
    else
        statbuf->st_mode |= (_S_IREAD | _S_IWRITE) | ((_S_IREAD | _S_IWRITE) >> 3) | ((_S_IREAD | _S_IWRITE) >> 6);

    FILETIME_TO_TIMESPEC(statbuf->st_atim, file_info.BasicInformation.LastAccessTime);
    FILETIME_TO_TIMESPEC(statbuf->st_ctim, file_info.BasicInformation.ChangeTime);
    FILETIME_TO_TIMESPEC(statbuf->st_mtim, file_info.BasicInformation.LastWriteTime);
    FILETIME_TO_TIMESPEC(statbuf->st_birthtim, file_info.BasicInformation.CreationTime);

    statbuf->st_ino = file_info.InternalInformation.IndexNumber.QuadPart;

    /* st_blocks contains the on-disk allocation size in 512-byte units. */
    statbuf->st_blocks = file_info.StandardInformation.AllocationSize.QuadPart >> 9ULL;

    statbuf->st_nlink = file_info.StandardInformation.NumberOfLinks;

    /* The st_blksize is supposed to be the 'optimal' number of bytes for reading
   * and writing to the disk. That is, for any definition of 'optimal' - it's
   * supposed to at least avoid read-update-write behavior when writing to the
   * disk.
   *
   * However nobody knows this and even fewer people actually use this value,
   * and in order to fill it out we'd have to make another syscall to query the
   * volume for FILE_FS_SECTOR_SIZE_INFORMATION.
   *
   * Therefore we'll just report a sensible value that's quite commonly okay
   * on modern hardware.
   */
    statbuf->st_blksize = 2048;

    /* Todo: set st_flags to something meaningful. Also provide a wrapper for
   * chattr(2).
   */
    statbuf->st_flags = 0;

    /* Windows has nothing sensible to say about these values, so they'll just
   * remain empty.
   */
    statbuf->st_gid = 0;
    statbuf->st_uid = 0;
    statbuf->st_rdev = 0;
    statbuf->st_gen = 0;

    return 0;
}

INLINE static void fs__stat_prepare_path(WCHAR* pathw)
{
    size_t len = wcslen(pathw);

    /* TODO: ignore namespaced paths. */
    if (len > 1 && pathw[len - 2] != L':' && (pathw[len - 1] == L'\\' || pathw[len - 1] == L'/')) {
        pathw[len - 1] = '\0';
    }
}

INLINE static void fs__stat_impl(uv_fs_t* req, int do_lstat)
{
    HANDLE handle;
    DWORD flags;

    flags = FILE_FLAG_BACKUP_SEMANTICS;
    if (do_lstat) {
        flags |= FILE_FLAG_OPEN_REPARSE_POINT;
    }

    handle = CreateFileW(req->file.pathw,
        FILE_READ_ATTRIBUTES,
        FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
        NULL,
        OPEN_EXISTING,
        flags,
        NULL);
    if (handle == INVALID_HANDLE_VALUE) {
        SET_REQ_WIN32_ERROR(req, GetLastError());
        return;
    }

    if (fs__stat_handle(handle, &req->statbuf) != 0) {
        DWORD error = GetLastError();
        if (do_lstat && error == ERROR_SYMLINK_NOT_SUPPORTED) {
            /* We opened a reparse point but it was not a symlink. Try again. */
            fs__stat_impl(req, 0);

        } else {
            /* Stat failed. */
            SET_REQ_WIN32_ERROR(req, GetLastError());
        }

        CloseHandle(handle);
        return;
    }

    req->ptr = &req->statbuf;
    req->result = 0;
    CloseHandle(handle);
}

static void fs__stat(uv_fs_t* req)
{
    fs__stat_prepare_path(req->file.pathw);
    fs__stat_impl(req, 0);
}

static void fs__lstat(uv_fs_t* req)
{
    fs__stat_prepare_path(req->file.pathw);
    fs__stat_impl(req, 1);
}

static void fs__fstat(uv_fs_t* req)
{
    int fd = req->file.fd;
    HANDLE handle;

    VERIFY_FD(fd, req);

    handle = uv__get_osfhandle(fd);

    if (handle == INVALID_HANDLE_VALUE) {
        SET_REQ_WIN32_ERROR(req, ERROR_INVALID_HANDLE);
        return;
    }

    if (fs__stat_handle(handle, &req->statbuf) != 0) {
        SET_REQ_WIN32_ERROR(req, GetLastError());
        return;
    }

    req->ptr = &req->statbuf;
    req->result = 0;
}

static void fs__rename(uv_fs_t* req)
{
    if (!MoveFileExW(req->file.pathw, req->fs.info.new_pathw, MOVEFILE_REPLACE_EXISTING)) {
        SET_REQ_WIN32_ERROR(req, GetLastError());
        return;
    }

    SET_REQ_RESULT(req, 0);
}

INLINE static void fs__sync_impl(uv_fs_t* req)
{
    int fd = req->file.fd;
    int result;

    VERIFY_FD(fd, req);

    result = FlushFileBuffers(uv__get_osfhandle(fd)) ? 0 : -1;
    if (result == -1) {
        SET_REQ_WIN32_ERROR(req, GetLastError());
    } else {
        SET_REQ_RESULT(req, result);
    }
}

static void fs__fsync(uv_fs_t* req)
{
    fs__sync_impl(req);
}

static void fs__fdatasync(uv_fs_t* req)
{
    fs__sync_impl(req);
}

static void fs__ftruncate(uv_fs_t* req)
{
    int fd = req->file.fd;
    HANDLE handle;
    NTSTATUS status;
    IO_STATUS_BLOCK io_status;
    FILE_END_OF_FILE_INFORMATION eof_info;

    VERIFY_FD(fd, req);

    handle = uv__get_osfhandle(fd);

    eof_info.EndOfFile.QuadPart = req->fs.info.offset;

    status = pNtSetInformationFile(handle,
        &io_status,
        &eof_info,
        sizeof eof_info,
        FileEndOfFileInformation);

    if (NT_SUCCESS(status)) {
        SET_REQ_RESULT(req, 0);
    } else {
        SET_REQ_WIN32_ERROR(req, pRtlNtStatusToDosError(status));
    }
}

static void fs__sendfile(uv_fs_t* req)
{
    int fd_in = req->file.fd, fd_out = req->fs.info.fd_out;
    size_t length = req->fs.info.bufsml[0].len;
    int64_t offset = req->fs.info.offset;
    const size_t max_buf_size = 65536;
    size_t buf_size = length < max_buf_size ? length : max_buf_size;
    int n, result = 0;
    int64_t result_offset = 0;
    char* buf = (char*)uv__malloc(buf_size);
    if (!buf) {
        uv_fatal_error(ERROR_OUTOFMEMORY, "uv__malloc");
    }

    if (offset != -1) {
        result_offset = _lseeki64(fd_in, offset, SEEK_SET);
    }

    if (result_offset == -1) {
        result = -1;
    } else {
        while (length > 0) {
            n = _read(fd_in, buf, length < buf_size ? length : buf_size);
            if (n == 0) {
                break;
            } else if (n == -1) {
                result = -1;
                break;
            }

            length -= n;

            n = _write(fd_out, buf, n);
            if (n == -1) {
                result = -1;
                break;
            }

            result += n;
        }
    }

    uv__free(buf);

    SET_REQ_RESULT(req, result);
}

static void fs__access(uv_fs_t* req)
{
    DWORD attr = GetFileAttributesW(req->file.pathw);

    if (attr == INVALID_FILE_ATTRIBUTES) {
        SET_REQ_WIN32_ERROR(req, GetLastError());
        return;
    }

    /*
   * Access is possible if
   * - write access wasn't requested,
   * - or the file isn't read-only,
   * - or it's a directory.
   * (Directories cannot be read-only on Windows.)
   */
    if (!(req->flags & W_OK) || !(attr & FILE_ATTRIBUTE_READONLY) || (attr & FILE_ATTRIBUTE_DIRECTORY)) {
        SET_REQ_RESULT(req, 0);
    } else {
        SET_REQ_WIN32_ERROR(req, UV_EPERM);
    }
}

static void fs__chmod(uv_fs_t* req)
{
    int result = _wchmod(req->file.pathw, req->fs.info.mode);
    SET_REQ_RESULT(req, result);
}

static void fs__fchmod(uv_fs_t* req)
{
    int fd = req->file.fd;
    HANDLE handle;
    NTSTATUS nt_status;
    IO_STATUS_BLOCK io_status;
    FILE_BASIC_INFORMATION file_info;

    VERIFY_FD(fd, req);

    handle = uv__get_osfhandle(fd);

    nt_status = pNtQueryInformationFile(handle,
        &io_status,
        &file_info,
        sizeof file_info,
        FileBasicInformation);

    if (!NT_SUCCESS(nt_status)) {
        SET_REQ_WIN32_ERROR(req, pRtlNtStatusToDosError(nt_status));
        return;
    }

    if (req->fs.info.mode & _S_IWRITE) {
        file_info.FileAttributes &= ~FILE_ATTRIBUTE_READONLY;
    } else {
        file_info.FileAttributes |= FILE_ATTRIBUTE_READONLY;
    }

    nt_status = pNtSetInformationFile(handle,
        &io_status,
        &file_info,
        sizeof file_info,
        FileBasicInformation);

    if (!NT_SUCCESS(nt_status)) {
        SET_REQ_WIN32_ERROR(req, pRtlNtStatusToDosError(nt_status));
        return;
    }

    SET_REQ_SUCCESS(req);
}

INLINE static int fs__utime_handle(HANDLE handle, double atime, double mtime)
{
    FILETIME filetime_a, filetime_m;

    TIME_T_TO_FILETIME((time_t)atime, &filetime_a);
    TIME_T_TO_FILETIME((time_t)mtime, &filetime_m);

    if (!SetFileTime(handle, NULL, &filetime_a, &filetime_m)) {
        return -1;
    }

    return 0;
}

static void fs__utime(uv_fs_t* req)
{
    HANDLE handle;

    handle = CreateFileW(req->file.pathw,
        FILE_WRITE_ATTRIBUTES,
        FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
        NULL,
        OPEN_EXISTING,
        FILE_FLAG_BACKUP_SEMANTICS,
        NULL);

    if (handle == INVALID_HANDLE_VALUE) {
        SET_REQ_WIN32_ERROR(req, GetLastError());
        return;
    }

    if (fs__utime_handle(handle, req->fs.time.atime, req->fs.time.mtime) != 0) {
        SET_REQ_WIN32_ERROR(req, GetLastError());
        CloseHandle(handle);
        return;
    }

    CloseHandle(handle);

    req->result = 0;
}

static void fs__futime(uv_fs_t* req)
{
    int fd = req->file.fd;
    HANDLE handle;
    VERIFY_FD(fd, req);

    handle = uv__get_osfhandle(fd);

    if (handle == INVALID_HANDLE_VALUE) {
        SET_REQ_WIN32_ERROR(req, ERROR_INVALID_HANDLE);
        return;
    }

    if (fs__utime_handle(handle, req->fs.time.atime, req->fs.time.mtime) != 0) {
        SET_REQ_WIN32_ERROR(req, GetLastError());
        return;
    }

    req->result = 0;
}

static void fs__link(uv_fs_t* req)
{
    DWORD r = CreateHardLinkW(req->fs.info.new_pathw, req->file.pathw, NULL);
    if (r == 0) {
        SET_REQ_WIN32_ERROR(req, GetLastError());
    } else {
        req->result = 0;
    }
}

static void fs__create_junction(uv_fs_t* req, const WCHAR* path,
    const WCHAR* new_path)
{
    HANDLE handle = INVALID_HANDLE_VALUE;
    REPARSE_DATA_BUFFER* buffer = NULL;
    int created = 0;
    int target_len;
    int is_absolute, is_long_path;
    int needed_buf_size, used_buf_size, used_data_size, path_buf_len;
    int start, len, i;
    int add_slash;
    DWORD bytes;
    WCHAR* path_buf;

    target_len = wcslen(path);
    is_long_path = wcsncmp(path, LONG_PATH_PREFIX, LONG_PATH_PREFIX_LEN) == 0;

    if (is_long_path) {
        is_absolute = 1;
    } else {
        is_absolute = target_len >= 3 && IS_LETTER(path[0]) && path[1] == L':' && IS_SLASH(path[2]);
    }

    if (!is_absolute) {
        /* Not supporting relative paths */
        SET_REQ_UV_ERROR(req, UV_EINVAL, ERROR_NOT_SUPPORTED);
        return;
    }

    /* Do a pessimistic calculation of the required buffer size */
    needed_buf_size = FIELD_OFFSET(REPARSE_DATA_BUFFER, MountPointReparseBuffer.PathBuffer) + JUNCTION_PREFIX_LEN * sizeof(WCHAR) + 2 * (target_len + 2) * sizeof(WCHAR);

    /* Allocate the buffer */
    buffer = (REPARSE_DATA_BUFFER*)uv__malloc(needed_buf_size);
    if (!buffer) {
        uv_fatal_error(ERROR_OUTOFMEMORY, "uv__malloc");
    }

    /* Grab a pointer to the part of the buffer where filenames go */
    path_buf = (WCHAR*)&(buffer->MountPointReparseBuffer.PathBuffer);
    path_buf_len = 0;

    /* Copy the substitute (internal) target path */
    start = path_buf_len;

    wcsncpy((WCHAR*)&path_buf[path_buf_len], JUNCTION_PREFIX,
        JUNCTION_PREFIX_LEN);
    path_buf_len += JUNCTION_PREFIX_LEN;

    add_slash = 0;
    for (i = is_long_path ? LONG_PATH_PREFIX_LEN : 0; path[i] != L'\0'; i++) {
        if (IS_SLASH(path[i])) {
            add_slash = 1;
            continue;
        }

        if (add_slash) {
            path_buf[path_buf_len++] = L'\\';
            add_slash = 0;
        }

        path_buf[path_buf_len++] = path[i];
    }
    path_buf[path_buf_len++] = L'\\';
    len = path_buf_len - start;

    /* Set the info about the substitute name */
    buffer->MountPointReparseBuffer.SubstituteNameOffset = start * sizeof(WCHAR);
    buffer->MountPointReparseBuffer.SubstituteNameLength = len * sizeof(WCHAR);

    /* Insert null terminator */
    path_buf[path_buf_len++] = L'\0';

    /* Copy the print name of the target path */
    start = path_buf_len;
    add_slash = 0;
    for (i = is_long_path ? LONG_PATH_PREFIX_LEN : 0; path[i] != L'\0'; i++) {
        if (IS_SLASH(path[i])) {
            add_slash = 1;
            continue;
        }

        if (add_slash) {
            path_buf[path_buf_len++] = L'\\';
            add_slash = 0;
        }

        path_buf[path_buf_len++] = path[i];
    }
    len = path_buf_len - start;
    if (len == 2) {
        path_buf[path_buf_len++] = L'\\';
        len++;
    }

    /* Set the info about the print name */
    buffer->MountPointReparseBuffer.PrintNameOffset = start * sizeof(WCHAR);
    buffer->MountPointReparseBuffer.PrintNameLength = len * sizeof(WCHAR);

    /* Insert another null terminator */
    path_buf[path_buf_len++] = L'\0';

    /* Calculate how much buffer space was actually used */
    used_buf_size = FIELD_OFFSET(REPARSE_DATA_BUFFER, MountPointReparseBuffer.PathBuffer) + path_buf_len * sizeof(WCHAR);
    used_data_size = used_buf_size - FIELD_OFFSET(REPARSE_DATA_BUFFER, MountPointReparseBuffer);

    /* Put general info in the data buffer */
    buffer->ReparseTag = IO_REPARSE_TAG_MOUNT_POINT;
    buffer->ReparseDataLength = used_data_size;
    buffer->Reserved = 0;

    /* Create a new directory */
    if (!CreateDirectoryW(new_path, NULL)) {
        SET_REQ_WIN32_ERROR(req, GetLastError());
        goto error;
    }
    created = 1;

    /* Open the directory */
    handle = CreateFileW(new_path,
        GENERIC_WRITE,
        0,
        NULL,
        OPEN_EXISTING,
        FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OPEN_REPARSE_POINT,
        NULL);
    if (handle == INVALID_HANDLE_VALUE) {
        SET_REQ_WIN32_ERROR(req, GetLastError());
        goto error;
    }

    /* Create the actual reparse point */
    if (!DeviceIoControl(handle,
            FSCTL_SET_REPARSE_POINT,
            buffer,
            used_buf_size,
            NULL,
            0,
            &bytes,
            NULL)) {
        SET_REQ_WIN32_ERROR(req, GetLastError());
        goto error;
    }

    /* Clean up */
    CloseHandle(handle);
    uv__free(buffer);

    SET_REQ_RESULT(req, 0);
    return;

error:
    uv__free(buffer);

    if (handle != INVALID_HANDLE_VALUE) {
        CloseHandle(handle);
    }

    if (created) {
        RemoveDirectoryW(new_path);
    }
}

static void fs__symlink(uv_fs_t* req)
{
    WCHAR* pathw = req->file.pathw;
    WCHAR* new_pathw = req->fs.info.new_pathw;
    int flags = req->fs.info.file_flags;
    int result;

    if (flags & UV_FS_SYMLINK_JUNCTION) {
        fs__create_junction(req, pathw, new_pathw);
    } else if (pCreateSymbolicLinkW) {
        result = pCreateSymbolicLinkW(new_pathw,
                     pathw,
                     flags & UV_FS_SYMLINK_DIR ? SYMBOLIC_LINK_FLAG_DIRECTORY : 0)
            ? 0
            : -1;
        if (result == -1) {
            SET_REQ_WIN32_ERROR(req, GetLastError());
        } else {
            SET_REQ_RESULT(req, result);
        }
    } else {
        SET_REQ_UV_ERROR(req, UV_ENOSYS, ERROR_NOT_SUPPORTED);
    }
}

static void fs__readlink(uv_fs_t* req)
{
    HANDLE handle;

    handle = CreateFileW(req->file.pathw,
        0,
        0,
        NULL,
        OPEN_EXISTING,
        FILE_FLAG_OPEN_REPARSE_POINT | FILE_FLAG_BACKUP_SEMANTICS,
        NULL);

    if (handle == INVALID_HANDLE_VALUE) {
        SET_REQ_WIN32_ERROR(req, GetLastError());
        return;
    }

    if (fs__readlink_handle(handle, (char**)&req->ptr, NULL) != 0) {
        SET_REQ_WIN32_ERROR(req, GetLastError());
        CloseHandle(handle);
        return;
    }

    req->flags |= UV_FS_FREE_PTR;
    SET_REQ_RESULT(req, 0);

    CloseHandle(handle);
}

static size_t fs__realpath_handle(HANDLE handle, char** realpath_ptr)
{
    int r;
    DWORD w_realpath_len;
    WCHAR* w_realpath_ptr = NULL;
    WCHAR* w_realpath_buf;

    w_realpath_len = pGetFinalPathNameByHandleW(handle, NULL, 0, VOLUME_NAME_DOS);
    if (w_realpath_len == 0) {
        return -1;
    }

    w_realpath_buf = uv__malloc((w_realpath_len + 1) * sizeof(WCHAR));
    if (w_realpath_buf == NULL) {
        SetLastError(ERROR_OUTOFMEMORY);
        return -1;
    }
    w_realpath_ptr = w_realpath_buf;

    if (pGetFinalPathNameByHandleW(handle,
            w_realpath_ptr,
            w_realpath_len,
            VOLUME_NAME_DOS)
        == 0) {
        uv__free(w_realpath_buf);
        SetLastError(ERROR_INVALID_HANDLE);
        return -1;
    }

    /* convert UNC path to long path */
    if (wcsncmp(w_realpath_ptr,
            UNC_PATH_PREFIX,
            UNC_PATH_PREFIX_LEN)
        == 0) {
        w_realpath_ptr += 6;
        *w_realpath_ptr = L'\\';
        w_realpath_len -= 6;
    } else if (wcsncmp(w_realpath_ptr,
                   LONG_PATH_PREFIX,
                   LONG_PATH_PREFIX_LEN)
        == 0) {
        w_realpath_ptr += 4;
        w_realpath_len -= 4;
    } else {
        uv__free(w_realpath_buf);
        SetLastError(ERROR_INVALID_HANDLE);
        return -1;
    }

    r = fs__wide_to_utf8(w_realpath_ptr, w_realpath_len, realpath_ptr, NULL);
    uv__free(w_realpath_buf);
    return r;
}

static void fs__realpath(uv_fs_t* req)
{
    HANDLE handle;

    if (!pGetFinalPathNameByHandleW) {
        SET_REQ_UV_ERROR(req, UV_ENOSYS, ERROR_NOT_SUPPORTED);
        return;
    }

    handle = CreateFileW(req->file.pathw,
        0,
        0,
        NULL,
        OPEN_EXISTING,
        FILE_ATTRIBUTE_NORMAL | FILE_FLAG_BACKUP_SEMANTICS,
        NULL);
    if (handle == INVALID_HANDLE_VALUE) {
        SET_REQ_WIN32_ERROR(req, GetLastError());
        return;
    }

    if (fs__realpath_handle(handle, (char**)&req->ptr) == -1) {
        CloseHandle(handle);
        SET_REQ_WIN32_ERROR(req, GetLastError());
        return;
    }

    CloseHandle(handle);
    req->flags |= UV_FS_FREE_PTR;
    SET_REQ_RESULT(req, 0);
}

static void fs__chown(uv_fs_t* req)
{
    req->result = 0;
}

static void fs__fchown(uv_fs_t* req)
{
    req->result = 0;
}

static void uv__fs_work(struct uv__work* w)
{
    uv_fs_t* req;

    req = container_of(w, uv_fs_t, work_req);
    assert(req->type == UV_FS);

#define XX(uc, lc)     \
    case UV_FS_##uc:   \
        fs__##lc(req); \
        break;
    switch (req->fs_type) {
        XX(OPEN, open)
        XX(CLOSE, close)
        XX(READ, read)
        XX(WRITE, write)
        XX(SENDFILE, sendfile)
        XX(STAT, stat)
        XX(LSTAT, lstat)
        XX(FSTAT, fstat)
        XX(FTRUNCATE, ftruncate)
        XX(UTIME, utime)
        XX(FUTIME, futime)
        XX(ACCESS, access)
        XX(CHMOD, chmod)
        XX(FCHMOD, fchmod)
        XX(FSYNC, fsync)
        XX(FDATASYNC, fdatasync)
        XX(UNLINK, unlink)
        XX(RMDIR, rmdir)
        XX(MKDIR, mkdir)
        XX(MKDTEMP, mkdtemp)
        XX(RENAME, rename)
        XX(SCANDIR, scandir)
        XX(LINK, link)
        XX(SYMLINK, symlink)
        XX(READLINK, readlink)
        XX(REALPATH, realpath)
        XX(CHOWN, chown)
        XX(FCHOWN, fchown);
    default:
        assert(!"bad uv_fs_type");
    }
}

static void uv__fs_done(struct uv__work* w, int status)
{
    uv_fs_t* req;

    req = container_of(w, uv_fs_t, work_req);
    uv__req_unregister(req->loop, req);

    if (status == UV_ECANCELED) {
        assert(req->result == 0);
        req->result = UV_ECANCELED;
    }

    req->cb(req);
}

void uv_fs_req_cleanup(uv_fs_t* req)
{
    if (req->flags & UV_FS_CLEANEDUP)
        return;

    if (req->flags & UV_FS_FREE_PATHS)
        uv__free(req->file.pathw);

    if (req->flags & UV_FS_FREE_PTR) {
        if (req->fs_type == UV_FS_SCANDIR && req->ptr != NULL)
            uv__fs_scandir_cleanup(req);
        else
            uv__free(req->ptr);
    }

    req->path = NULL;
    req->file.pathw = NULL;
    req->fs.info.new_pathw = NULL;
    req->ptr = NULL;

    req->flags |= UV_FS_CLEANEDUP;
}

int uv_fs_open(uv_loop_t* loop, uv_fs_t* req, const char* path, int flags,
    int mode, uv_fs_cb cb)
{
    int err;

    uv_fs_req_init(loop, req, UV_FS_OPEN, cb);

    err = fs__capture_path(req, path, NULL, cb != NULL);
    if (err) {
        return uv_translate_sys_error(err);
    }

    req->fs.info.file_flags = flags;
    req->fs.info.mode = mode;

    if (cb) {
        QUEUE_FS_TP_JOB(loop, req);
        return 0;
    } else {
        fs__open(req);
        return req->result;
    }
}

int uv_fs_close(uv_loop_t* loop, uv_fs_t* req, uv_file fd, uv_fs_cb cb)
{
    uv_fs_req_init(loop, req, UV_FS_CLOSE, cb);
    req->file.fd = fd;

    if (cb) {
        QUEUE_FS_TP_JOB(loop, req);
        return 0;
    } else {
        fs__close(req);
        return req->result;
    }
}

int uv_fs_read(uv_loop_t* loop,
    uv_fs_t* req,
    uv_file fd,
    const uv_buf_t bufs[],
    unsigned int nbufs,
    int64_t offset,
    uv_fs_cb cb)
{
    if (bufs == NULL || nbufs == 0)
        return UV_EINVAL;

    uv_fs_req_init(loop, req, UV_FS_READ, cb);

    req->file.fd = fd;

    req->fs.info.nbufs = nbufs;
    req->fs.info.bufs = req->fs.info.bufsml;
    if (nbufs > ARRAY_SIZE(req->fs.info.bufsml))
        req->fs.info.bufs = uv__malloc(nbufs * sizeof(*bufs));

    if (req->fs.info.bufs == NULL)
        return UV_ENOMEM;

    memcpy(req->fs.info.bufs, bufs, nbufs * sizeof(*bufs));

    req->fs.info.offset = offset;

    if (cb) {
        QUEUE_FS_TP_JOB(loop, req);
        return 0;
    } else {
        fs__read(req);
        return req->result;
    }
}

int uv_fs_write(uv_loop_t* loop,
    uv_fs_t* req,
    uv_file fd,
    const uv_buf_t bufs[],
    unsigned int nbufs,
    int64_t offset,
    uv_fs_cb cb)
{
    if (bufs == NULL || nbufs == 0)
        return UV_EINVAL;

    uv_fs_req_init(loop, req, UV_FS_WRITE, cb);

    req->file.fd = fd;

    req->fs.info.nbufs = nbufs;
    req->fs.info.bufs = req->fs.info.bufsml;
    if (nbufs > ARRAY_SIZE(req->fs.info.bufsml))
        req->fs.info.bufs = uv__malloc(nbufs * sizeof(*bufs));

    if (req->fs.info.bufs == NULL)
        return UV_ENOMEM;

    memcpy(req->fs.info.bufs, bufs, nbufs * sizeof(*bufs));

    req->fs.info.offset = offset;

    if (cb) {
        QUEUE_FS_TP_JOB(loop, req);
        return 0;
    } else {
        fs__write(req);
        return req->result;
    }
}

int uv_fs_unlink(uv_loop_t* loop, uv_fs_t* req, const char* path,
    uv_fs_cb cb)
{
    int err;

    uv_fs_req_init(loop, req, UV_FS_UNLINK, cb);

    err = fs__capture_path(req, path, NULL, cb != NULL);
    if (err) {
        return uv_translate_sys_error(err);
    }

    if (cb) {
        QUEUE_FS_TP_JOB(loop, req);
        return 0;
    } else {
        fs__unlink(req);
        return req->result;
    }
}

int uv_fs_mkdir(uv_loop_t* loop, uv_fs_t* req, const char* path, int mode,
    uv_fs_cb cb)
{
    int err;

    uv_fs_req_init(loop, req, UV_FS_MKDIR, cb);

    err = fs__capture_path(req, path, NULL, cb != NULL);
    if (err) {
        return uv_translate_sys_error(err);
    }

    req->fs.info.mode = mode;

    if (cb) {
        QUEUE_FS_TP_JOB(loop, req);
        return 0;
    } else {
        fs__mkdir(req);
        return req->result;
    }
}

int uv_fs_mkdtemp(uv_loop_t* loop, uv_fs_t* req, const char* tpl,
    uv_fs_cb cb)
{
    int err;

    uv_fs_req_init(loop, req, UV_FS_MKDTEMP, cb);

    err = fs__capture_path(req, tpl, NULL, TRUE);
    if (err)
        return uv_translate_sys_error(err);

    if (cb) {
        QUEUE_FS_TP_JOB(loop, req);
        return 0;
    } else {
        fs__mkdtemp(req);
        return req->result;
    }
}

int uv_fs_rmdir(uv_loop_t* loop, uv_fs_t* req, const char* path, uv_fs_cb cb)
{
    int err;

    uv_fs_req_init(loop, req, UV_FS_RMDIR, cb);

    err = fs__capture_path(req, path, NULL, cb != NULL);
    if (err) {
        return uv_translate_sys_error(err);
    }

    if (cb) {
        QUEUE_FS_TP_JOB(loop, req);
        return 0;
    } else {
        fs__rmdir(req);
        return req->result;
    }
}

int uv_fs_scandir(uv_loop_t* loop, uv_fs_t* req, const char* path, int flags,
    uv_fs_cb cb)
{
    int err;

    uv_fs_req_init(loop, req, UV_FS_SCANDIR, cb);

    err = fs__capture_path(req, path, NULL, cb != NULL);
    if (err) {
        return uv_translate_sys_error(err);
    }

    req->fs.info.file_flags = flags;

    if (cb) {
        QUEUE_FS_TP_JOB(loop, req);
        return 0;
    } else {
        fs__scandir(req);
        return req->result;
    }
}

int uv_fs_link(uv_loop_t* loop, uv_fs_t* req, const char* path,
    const char* new_path, uv_fs_cb cb)
{
    int err;

    uv_fs_req_init(loop, req, UV_FS_LINK, cb);

    err = fs__capture_path(req, path, new_path, cb != NULL);
    if (err) {
        return uv_translate_sys_error(err);
    }

    if (cb) {
        QUEUE_FS_TP_JOB(loop, req);
        return 0;
    } else {
        fs__link(req);
        return req->result;
    }
}

int uv_fs_symlink(uv_loop_t* loop, uv_fs_t* req, const char* path,
    const char* new_path, int flags, uv_fs_cb cb)
{
    int err;

    uv_fs_req_init(loop, req, UV_FS_SYMLINK, cb);

    err = fs__capture_path(req, path, new_path, cb != NULL);
    if (err) {
        return uv_translate_sys_error(err);
    }

    req->fs.info.file_flags = flags;

    if (cb) {
        QUEUE_FS_TP_JOB(loop, req);
        return 0;
    } else {
        fs__symlink(req);
        return req->result;
    }
}

int uv_fs_readlink(uv_loop_t* loop, uv_fs_t* req, const char* path,
    uv_fs_cb cb)
{
    int err;

    uv_fs_req_init(loop, req, UV_FS_READLINK, cb);

    err = fs__capture_path(req, path, NULL, cb != NULL);
    if (err) {
        return uv_translate_sys_error(err);
    }

    if (cb) {
        QUEUE_FS_TP_JOB(loop, req);
        return 0;
    } else {
        fs__readlink(req);
        return req->result;
    }
}

int uv_fs_realpath(uv_loop_t* loop, uv_fs_t* req, const char* path,
    uv_fs_cb cb)
{
    int err;

    if (!req || !path) {
        return UV_EINVAL;
    }

    uv_fs_req_init(loop, req, UV_FS_REALPATH, cb);

    err = fs__capture_path(req, path, NULL, cb != NULL);
    if (err) {
        return uv_translate_sys_error(err);
    }

    if (cb) {
        QUEUE_FS_TP_JOB(loop, req);
        return 0;
    } else {
        fs__realpath(req);
        return req->result;
    }
}

int uv_fs_chown(uv_loop_t* loop, uv_fs_t* req, const char* path, uv_uid_t uid,
    uv_gid_t gid, uv_fs_cb cb)
{
    int err;

    uv_fs_req_init(loop, req, UV_FS_CHOWN, cb);

    err = fs__capture_path(req, path, NULL, cb != NULL);
    if (err) {
        return uv_translate_sys_error(err);
    }

    if (cb) {
        QUEUE_FS_TP_JOB(loop, req);
        return 0;
    } else {
        fs__chown(req);
        return req->result;
    }
}

int uv_fs_fchown(uv_loop_t* loop, uv_fs_t* req, uv_file fd, uv_uid_t uid,
    uv_gid_t gid, uv_fs_cb cb)
{
    uv_fs_req_init(loop, req, UV_FS_FCHOWN, cb);

    if (cb) {
        QUEUE_FS_TP_JOB(loop, req);
        return 0;
    } else {
        fs__fchown(req);
        return req->result;
    }
}

int uv_fs_stat(uv_loop_t* loop, uv_fs_t* req, const char* path, uv_fs_cb cb)
{
    int err;

    uv_fs_req_init(loop, req, UV_FS_STAT, cb);

    err = fs__capture_path(req, path, NULL, cb != NULL);
    if (err) {
        return uv_translate_sys_error(err);
    }

    if (cb) {
        QUEUE_FS_TP_JOB(loop, req);
        return 0;
    } else {
        fs__stat(req);
        return req->result;
    }
}

int uv_fs_lstat(uv_loop_t* loop, uv_fs_t* req, const char* path, uv_fs_cb cb)
{
    int err;

    uv_fs_req_init(loop, req, UV_FS_LSTAT, cb);

    err = fs__capture_path(req, path, NULL, cb != NULL);
    if (err) {
        return uv_translate_sys_error(err);
    }

    if (cb) {
        QUEUE_FS_TP_JOB(loop, req);
        return 0;
    } else {
        fs__lstat(req);
        return req->result;
    }
}

int uv_fs_fstat(uv_loop_t* loop, uv_fs_t* req, uv_file fd, uv_fs_cb cb)
{
    uv_fs_req_init(loop, req, UV_FS_FSTAT, cb);
    req->file.fd = fd;

    if (cb) {
        QUEUE_FS_TP_JOB(loop, req);
        return 0;
    } else {
        fs__fstat(req);
        return req->result;
    }
}

int uv_fs_rename(uv_loop_t* loop, uv_fs_t* req, const char* path,
    const char* new_path, uv_fs_cb cb)
{
    int err;

    uv_fs_req_init(loop, req, UV_FS_RENAME, cb);

    err = fs__capture_path(req, path, new_path, cb != NULL);
    if (err) {
        return uv_translate_sys_error(err);
    }

    if (cb) {
        QUEUE_FS_TP_JOB(loop, req);
        return 0;
    } else {
        fs__rename(req);
        return req->result;
    }
}

int uv_fs_fsync(uv_loop_t* loop, uv_fs_t* req, uv_file fd, uv_fs_cb cb)
{
    uv_fs_req_init(loop, req, UV_FS_FSYNC, cb);
    req->file.fd = fd;

    if (cb) {
        QUEUE_FS_TP_JOB(loop, req);
        return 0;
    } else {
        fs__fsync(req);
        return req->result;
    }
}

int uv_fs_fdatasync(uv_loop_t* loop, uv_fs_t* req, uv_file fd, uv_fs_cb cb)
{
    uv_fs_req_init(loop, req, UV_FS_FDATASYNC, cb);
    req->file.fd = fd;

    if (cb) {
        QUEUE_FS_TP_JOB(loop, req);
        return 0;
    } else {
        fs__fdatasync(req);
        return req->result;
    }
}

int uv_fs_ftruncate(uv_loop_t* loop, uv_fs_t* req, uv_file fd,
    int64_t offset, uv_fs_cb cb)
{
    uv_fs_req_init(loop, req, UV_FS_FTRUNCATE, cb);

    req->file.fd = fd;
    req->fs.info.offset = offset;

    if (cb) {
        QUEUE_FS_TP_JOB(loop, req);
        return 0;
    } else {
        fs__ftruncate(req);
        return req->result;
    }
}

int uv_fs_sendfile(uv_loop_t* loop, uv_fs_t* req, uv_file fd_out,
    uv_file fd_in, int64_t in_offset, size_t length, uv_fs_cb cb)
{
    uv_fs_req_init(loop, req, UV_FS_SENDFILE, cb);

    req->file.fd = fd_in;
    req->fs.info.fd_out = fd_out;
    req->fs.info.offset = in_offset;
    req->fs.info.bufsml[0].len = length;

    if (cb) {
        QUEUE_FS_TP_JOB(loop, req);
        return 0;
    } else {
        fs__sendfile(req);
        return req->result;
    }
}

int uv_fs_access(uv_loop_t* loop,
    uv_fs_t* req,
    const char* path,
    int flags,
    uv_fs_cb cb)
{
    int err;

    uv_fs_req_init(loop, req, UV_FS_ACCESS, cb);

    err = fs__capture_path(req, path, NULL, cb != NULL);
    if (err)
        return uv_translate_sys_error(err);

    req->flags = flags;

    if (cb) {
        QUEUE_FS_TP_JOB(loop, req);
        return 0;
    }

    fs__access(req);
    return req->result;
}

int uv_fs_chmod(uv_loop_t* loop, uv_fs_t* req, const char* path, int mode,
    uv_fs_cb cb)
{
    int err;

    uv_fs_req_init(loop, req, UV_FS_CHMOD, cb);

    err = fs__capture_path(req, path, NULL, cb != NULL);
    if (err) {
        return uv_translate_sys_error(err);
    }

    req->fs.info.mode = mode;

    if (cb) {
        QUEUE_FS_TP_JOB(loop, req);
        return 0;
    } else {
        fs__chmod(req);
        return req->result;
    }
}

int uv_fs_fchmod(uv_loop_t* loop, uv_fs_t* req, uv_file fd, int mode,
    uv_fs_cb cb)
{
    uv_fs_req_init(loop, req, UV_FS_FCHMOD, cb);

    req->file.fd = fd;
    req->fs.info.mode = mode;

    if (cb) {
        QUEUE_FS_TP_JOB(loop, req);
        return 0;
    } else {
        fs__fchmod(req);
        return req->result;
    }
}

int uv_fs_utime(uv_loop_t* loop, uv_fs_t* req, const char* path, double atime,
    double mtime, uv_fs_cb cb)
{
    int err;

    uv_fs_req_init(loop, req, UV_FS_UTIME, cb);

    err = fs__capture_path(req, path, NULL, cb != NULL);
    if (err) {
        return uv_translate_sys_error(err);
    }

    req->fs.time.atime = atime;
    req->fs.time.mtime = mtime;

    if (cb) {
        QUEUE_FS_TP_JOB(loop, req);
        return 0;
    } else {
        fs__utime(req);
        return req->result;
    }
}

int uv_fs_futime(uv_loop_t* loop, uv_fs_t* req, uv_file fd, double atime,
    double mtime, uv_fs_cb cb)
{
    uv_fs_req_init(loop, req, UV_FS_FUTIME, cb);

    req->file.fd = fd;
    req->fs.time.atime = atime;
    req->fs.time.mtime = mtime;

    if (cb) {
        QUEUE_FS_TP_JOB(loop, req);
        return 0;
    } else {
        fs__futime(req);
        return req->result;
    }
}
